package jwu2.core;

import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * A Task is an assignment or job which has some name, a progress status
 * and completes at some point.
 * 
 * A task can consist of subtasks which can be defined ahead of time. If a task
 * has subtasks its progress will be the combined total of the progress of the
 * subtasks.
 * 
 * @author jbk
 */
public class Task implements TaskListener {
    private static Task singleton = new Task("Running tasks"); // A single root task allows monitoring of all running tasks

    private String name;
    private List<Task> subTasks;
    private Set<TaskListener> listeners;
    private boolean tainted = true; // True if this task or subtasks have changed and information needs to be recalculated
    private boolean hasCompleted = false;
    private float progress;
    private int completedSubtasks; // Number of already completed subtasks
    private String infoText = "";
    
    private Task mostRecentlyProgressedTask = null;

    
    /**
     * Creates a new task object
     */
    private Task(String name) {
        this.name = name;
        subTasks = new ArrayList<Task>();
        listeners = new HashSet<TaskListener>();
    }

    /**
     * Creates a new Task with a description
     * @param shortDescription Describes the task
     * @return A new task
     */
    public static Task createTask(String name){
        return singleton.createSubTask(name);
    }

    /**
     * Creates a new subtask of this task
     * @param shortDescription Description of the new subtask
     * @return The new subtask Task
     */
    public Task createSubTask(String name) {
        if (hasCompleted) throw new IllegalStateException("createSubTask() called on a Task which has already completed ("+this+":"+name+")");
        Task subTask = new Task(name);
        this.registerSubTask(subTask);
        return subTask;
    }


    /**
     * Adds the given TaskListener to the list of listeners
     * @param A tasklistener
     */
    public void addListener(TaskListener listener) {
        listeners.add(listener);
    }
    public void removeListener(TaskListener listener){
        listeners.remove(listener);
    }

    public void onProgressUpdate(Task task, Task origin) {
        // Inform our listeners
        for (TaskListener listener : listeners){
            listener.onProgressUpdate(this, origin);
        }
        tainted = true;
        mostRecentlyProgressedTask = task;
    }

    /**
     * Adds a task to the list of subtasks of this task and
     * tells the new subtask that this task wants to be
     * a listener.
     * @param subTask The subtask to register
     */
    private void registerSubTask(Task subTask) {
        subTasks.add(subTask);
        subTask.addListener(this);
        for (TaskListener listener : listeners){
            listener.onNewSubTask(this, subTask);
        }
    }
    
    /**
     * Sets the current progress of a task.
     * Cannot set a progress on a task with subtasks.
     * @param fraction The current progress between 0 and 1
     * @param infoText A short description or information message - for instance the current download speed
     * @throws IllegalStateException If progress was set on a task with subtasks
     */
    public void setProgress(float fraction, String infoText){
        if (!subTasks.isEmpty()) throw new IllegalStateException("setProgress("+fraction+") called on a Task that has subTasks ("+this+":"+name+")");
        if (hasCompleted) throw new IllegalStateException("setProgress("+fraction+") called on a Task which has already completed ("+this+":"+name+")");
        progress = fraction;
        this.infoText = infoText;
        onProgressUpdate(this, this);
    }

    /**
     * Sets the current progress of a task.
     * Cannot set a progress on a task with subtasks.
     * @param fraction The current progress between 0 and 1
     * @throws IllegalStateException If progress was set on a task with subtasks
     */
    public void setProgress(float fraction){
        setProgress(fraction, "");
    }
    
    /**
     * Returns the current progress
     */
    public float getProgress(){
        if (hasCompleted) return 1f;
        if (tainted){
            // Recalculate progress
            if (subTasks.isEmpty()){
                // Nothing to calculate
            } else {
                // We have subtasks
                int countTasks = subTasks.size() + completedSubtasks;
                float calc = completedSubtasks/(float)countTasks;
                for (Task subtask : subTasks){
                    calc += (subtask.getProgress()/(float)countTasks);
                }
                progress = calc;
            }
            tainted = false;
        }
        return progress;
    }

    public void onNewSubTask(Task task, Task subTask) {
        // Do nothing, we don't care
    }
    
    /**
     * Completes this task, no further updates can be done to this task.
     */
    public void setComplete(){
        setProgress(1f);
        hasCompleted = true;
        try {
            for (TaskListener listener : listeners){
                listener.onCompletion(this);
            }
        } catch (ConcurrentModificationException ex){
            ConcurrentModificationException e = new ConcurrentModificationException("Listener list modified while completing task, listeners need not remove themselves from the listener list in onCompleted() as it will be cleared automatically.");
            e.initCause(ex);
            throw e;
        }
        listeners.clear();
    }

    public void onCompletion(Task task) {
        subTasks.remove(task);
        completedSubtasks++;
        if (subTasks.isEmpty()){
            // Everything has completed
            completedSubtasks = 0;
            progress = 1f;
            for (TaskListener listener : listeners){
                listener.onInactive(this);
            }
        }
    }

    public String getName() {
        return name;
    }

    public List<Task> getSubTasks() {
        return subTasks;
    }

    public String getInfoText() {
        return infoText;
    }
    
    public static Task getRootTask() {
        return singleton;
    }

    /**
     * Returns the subtask (if any, null othewise) that has
     * most recently shown progress
     * @return The subtask that has most recently shown progress.
     */
    public Task getMostRecentlyProgressedTask() {
        return mostRecentlyProgressedTask;
    }

    public void onInactive(Task task) {
        // Do nothing, we don't care
    }
    
    public void log(String logMessage){
        for (TaskListener listener : listeners){
            listener.onLog(this, logMessage);
        }
    }

    public void onLog(Task task, String logMessage) {
        // Do nothing, we don't care
    }
}
